1   // Copyright 2006, 2007, 2008, 2009, 2010, 2011, 2012 The Apache Software Foundation
2   //
3   // Licensed under the Apache License, Version 2.0 (the "License");
4   // you may not use this file except in compliance with the License.
5   // You may obtain a copy of the License at
6   //
7   // http://www.apache.org/licenses/LICENSE-2.0
8   //
9   // Unless required by applicable law or agreed to in writing, software
10  // distributed under the License is distributed on an "AS IS" BASIS,
11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  // See the License for the specific language governing permissions and
13  // limitations under the License.
14  
15  package org.apache.tapestry5.internal.model;
16  
17  import org.apache.tapestry5.ioc.Location;
18  import org.apache.tapestry5.ioc.Resource;
19  import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
20  import org.apache.tapestry5.ioc.internal.util.InternalUtils;
21  import org.apache.tapestry5.ioc.util.IdAllocator;
22  import org.apache.tapestry5.model.*;
23  import org.slf4j.Logger;
24  
25  import java.util.Collections;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Set;
29  
30  /**
31   * Internal implementation of {@link org.apache.tapestry5.model.MutableComponentModel}.
32   */
33  public final class MutableComponentModelImpl implements MutableComponentModel
34  {
35      private final ComponentModel parentModel;
36  
37      private final Resource baseResource;
38  
39      private final String componentClassName;
40  
41      private final IdAllocator persistentFieldNameAllocator = new IdAllocator();
42  
43      private final Logger logger;
44  
45      private final boolean pageClass;
46  
47      private Map<String, ParameterModel> parameters;
48  
49      private Map<String, EmbeddedComponentModel> embeddedComponents;
50  
51      /**
52       * Maps from field name to strategy.
53       */
54      private Map<String, String> persistentFields;
55  
56      private List<String> mixinClassNames;
57  
58      private Map<String, String[]> mixinOrders;
59  
60      private boolean informalParametersSupported;
61  
62      private boolean mixinAfter;
63  
64      private Map<String, String> metaData;
65  
66      private Set<Class> handledRenderPhases;
67  
68      private Map<String, Boolean> handledEvents;
69  
70      private final String libraryName;
71  
72      private boolean handleActivationEventContext;
73  
74      public MutableComponentModelImpl(String componentClassName, Logger logger, Resource baseResource,
75                                       ComponentModel parentModel, boolean pageClass, String libraryName)
76      {
77          this.componentClassName = componentClassName;
78          this.logger = logger;
79          this.baseResource = baseResource;
80          this.parentModel = parentModel;
81          this.pageClass = pageClass;
82          this.libraryName = libraryName;
83  
84          // Default to false, explicitly set during page transformation
85          this.handleActivationEventContext = false;
86  
87          // Pre-allocate names from the parent, to avoid name collisions.
88  
89          if (this.parentModel != null)
90          {
91              for (String name : this.parentModel.getPersistentFieldNames())
92              {
93                  persistentFieldNameAllocator.allocateId(name);
94              }
95          }
96      }
97  
98      public String getLibraryName()
99      {
100         return libraryName;
101     }
102 
103     @Override
104     public String toString()
105     {
106         return String.format("ComponentModel[%s]", componentClassName);
107     }
108 
109     public Logger getLogger()
110     {
111         return logger;
112     }
113 
114     public Resource getBaseResource()
115     {
116         return baseResource;
117     }
118 
119     public String getComponentClassName()
120     {
121         return componentClassName;
122     }
123 
124     public void addParameter(String name, boolean required, boolean allowNull, String defaultBindingPrefix,
125                              boolean cached)
126     {
127         assert InternalUtils.isNonBlank(name);
128         assert InternalUtils.isNonBlank(defaultBindingPrefix);
129 
130         if (parameters == null)
131         {
132             parameters = CollectionFactory.newCaseInsensitiveMap();
133         }
134 
135         if (parameters.containsKey(name))
136         {
137             throw new IllegalArgumentException(String.format("Parameter '%s' of component class %s is already defined.", name, componentClassName));
138         }
139 
140         ParameterModel existingModel = getParameterModel(name);
141 
142         if (existingModel != null)
143         {
144             throw new IllegalArgumentException(String.format("Parameter '%s' of component class %s conflicts with the parameter defined by the %s base class.",
145                     name, componentClassName, existingModel.getComponentModel().getComponentClassName()));
146         }
147 
148         parameters.put(name, new ParameterModelImpl(this, name, required, allowNull, defaultBindingPrefix, cached));
149     }
150 
151     public void addParameter(String name, boolean required, boolean allowNull, String defaultBindingPrefix)
152     {
153         // assume /false/ for the default because:
154         // if the parameter is actually cached, the only effect will be to reduce that optimization
155         // in certain
156         // scenarios (mixin BindParameter). But if the value is NOT cached but we say it is,
157         // we'll get incorrect behavior.
158         addParameter(name, required, allowNull, defaultBindingPrefix, false);
159     }
160 
161     public ParameterModel getParameterModel(String parameterName)
162     {
163         ParameterModel result = InternalUtils.get(parameters, parameterName);
164 
165         if (result == null && parentModel != null)
166             result = parentModel.getParameterModel(parameterName);
167 
168         return result;
169     }
170 
171     public boolean isFormalParameter(String parameterName)
172     {
173         return getParameterModel(parameterName) != null;
174     }
175 
176     public List<String> getParameterNames()
177     {
178         List<String> names = CollectionFactory.newList();
179 
180         if (parameters != null)
181             names.addAll(parameters.keySet());
182 
183         if (parentModel != null)
184             names.addAll(parentModel.getParameterNames());
185 
186         Collections.sort(names);
187 
188         return names;
189     }
190 
191     public List<String> getDeclaredParameterNames()
192     {
193         return InternalUtils.sortedKeys(parameters);
194     }
195 
196     public MutableEmbeddedComponentModel addEmbeddedComponent(String id, String type, String componentClassName,
197                                                               boolean inheritInformalParameters, Location location)
198     {
199         // TODO: Parent compent model? Or would we simply override the parent?
200 
201         if (embeddedComponents == null)
202             embeddedComponents = CollectionFactory.newCaseInsensitiveMap();
203         else if (embeddedComponents.containsKey(id))
204             throw new IllegalArgumentException(String.format("Embedded component '%s' has already been defined for component class %s.", id, this.componentClassName));
205 
206         MutableEmbeddedComponentModel embedded = new MutableEmbeddedComponentModelImpl(id, type, componentClassName,
207                 this.componentClassName, inheritInformalParameters, location);
208 
209         embeddedComponents.put(id, embedded);
210 
211         return embedded; // So that parameters can be filled in
212     }
213 
214     public List<String> getEmbeddedComponentIds()
215     {
216         List<String> result = CollectionFactory.newList();
217 
218         if (embeddedComponents != null)
219             result.addAll(embeddedComponents.keySet());
220 
221         if (parentModel != null)
222             result.addAll(parentModel.getEmbeddedComponentIds());
223 
224         Collections.sort(result);
225 
226         return result;
227     }
228 
229     public EmbeddedComponentModel getEmbeddedComponentModel(String componentId)
230     {
231         EmbeddedComponentModel result = InternalUtils.get(embeddedComponents, componentId);
232 
233         if (result == null && parentModel != null)
234             result = parentModel.getEmbeddedComponentModel(componentId);
235 
236         return result;
237     }
238 
239     public String getFieldPersistenceStrategy(String fieldName)
240     {
241         String result = InternalUtils.get(persistentFields, fieldName);
242 
243         if (result == null && parentModel != null)
244             result = parentModel.getFieldPersistenceStrategy(fieldName);
245 
246         if (result == null)
247             throw new IllegalArgumentException(String.format("No field persistence strategy has been defined for field '%s'.", fieldName));
248 
249         return result;
250     }
251 
252     public List<String> getPersistentFieldNames()
253     {
254         return persistentFieldNameAllocator.getAllocatedIds();
255     }
256 
257     public String setFieldPersistenceStrategy(String fieldName, String strategy)
258     {
259         String logicalFieldName = persistentFieldNameAllocator.allocateId(fieldName);
260 
261         if (persistentFields == null)
262             persistentFields = CollectionFactory.newMap();
263 
264         persistentFields.put(logicalFieldName, strategy);
265 
266         return logicalFieldName;
267     }
268 
269     public boolean isRootClass()
270     {
271         return parentModel == null;
272     }
273 
274     public void addMixinClassName(String mixinClassName, String... order)
275     {
276         if (mixinClassNames == null)
277             mixinClassNames = CollectionFactory.newList();
278 
279         mixinClassNames.add(mixinClassName);
280         if (order != null && order.length > 0)
281         {
282             if (mixinOrders == null)
283                 mixinOrders = CollectionFactory.newCaseInsensitiveMap();
284             mixinOrders.put(mixinClassName, order);
285         }
286     }
287 
288     public List<String> getMixinClassNames()
289     {
290         List<String> result = CollectionFactory.newList();
291 
292         if (mixinClassNames != null)
293             result.addAll(mixinClassNames);
294 
295         if (parentModel != null)
296             result.addAll(parentModel.getMixinClassNames());
297 
298         Collections.sort(result);
299 
300         return result;
301     }
302 
303     public void enableSupportsInformalParameters()
304     {
305         informalParametersSupported = true;
306     }
307 
308     public boolean getSupportsInformalParameters()
309     {
310         return informalParametersSupported;
311     }
312 
313     public ComponentModel getParentModel()
314     {
315         return parentModel;
316     }
317 
318     public boolean isMixinAfter()
319     {
320         return mixinAfter;
321     }
322 
323     public void setMixinAfter(boolean mixinAfter)
324     {
325         this.mixinAfter = mixinAfter;
326     }
327 
328     public void setMeta(String key, String value)
329     {
330         assert InternalUtils.isNonBlank(key);
331         assert InternalUtils.isNonBlank(value);
332         if (metaData == null)
333             metaData = CollectionFactory.newCaseInsensitiveMap();
334 
335         // TODO: Error if duplicate?
336 
337         metaData.put(key, value);
338     }
339 
340     public void addRenderPhase(Class renderPhase)
341     {
342         assert renderPhase != null;
343         if (handledRenderPhases == null)
344             handledRenderPhases = CollectionFactory.newSet();
345 
346         handledRenderPhases.add(renderPhase);
347     }
348 
349     public void addEventHandler(String eventType)
350     {
351         if (handledEvents == null)
352             handledEvents = CollectionFactory.newCaseInsensitiveMap();
353 
354         handledEvents.put(eventType, true);
355     }
356 
357     public String getMeta(String key)
358     {
359         String result = InternalUtils.get(metaData, key);
360 
361         if (result == null && parentModel != null)
362             result = parentModel.getMeta(key);
363 
364         return result;
365     }
366 
367     public Set<Class> getHandledRenderPhases()
368     {
369         Set<Class> result = CollectionFactory.newSet();
370 
371         if (parentModel != null)
372             result.addAll(parentModel.getHandledRenderPhases());
373 
374         if (handledRenderPhases != null)
375             result.addAll(handledRenderPhases);
376 
377         return result;
378     }
379 
380     public boolean handlesEvent(String eventType)
381     {
382         if (InternalUtils.get(handledEvents, eventType) != null)
383             return true;
384 
385         return parentModel == null ? false : parentModel.handlesEvent(eventType);
386     }
387 
388     public String[] getOrderForMixin(String mixinClassName)
389     {
390         final String[] orders = InternalUtils.get(mixinOrders, mixinClassName);
391 
392         if (orders == null && parentModel != null)
393             return parentModel.getOrderForMixin(mixinClassName);
394 
395         return orders;
396     }
397 
398     public boolean isPage()
399     {
400         return pageClass;
401     }
402 
403     public void doHandleActivationEventContext()
404     {
405         this.handleActivationEventContext = true;
406     }
407 
408     public boolean handleActivationEventContext()
409     {
410         return this.handleActivationEventContext;
411     }
412 }